#include "cmd_parser.h"
#include "mem_manager.h"
#include "io_console.h"
#include "core_searchTree.h"

#include <string.h>

/////////////////////////////
// for parsing
#define MAX_TOKEN_SIZE	1024
#define MAX_STACK_DEPTH	16

char cmd_token[MAX_TOKEN_SIZE];

static char *cmd_line;
static int cmd_pos;
static int stackDepth;
static bool parseError;

////////////////////////////
// for commands
#define CMDTYPE_BASIC	0
#define CMDTYPE_COMBO	1

typedef struct
{
	char type;
	union
	{
		char *asCombo;
		TFuncPtr asBasic;
	}cmd;
}TCmdItem;

static CSearchTree *searchTree;

static void skipWhitespace()
{
	while ((cmd_line[cmd_pos]==' ')||(cmd_line[cmd_pos]=='\t')||(cmd_line[cmd_pos]=='\n')||(cmd_line[cmd_pos]=='\r'))
		cmd_pos++;
}


void cmd_init()
{
	//init parsing
	cmd_line = NULL;
	cmd_pos = 0;
	stackDepth = 0;
	parseError = false;

	//init commands
	g_alloc(searchTree, CSearchTree());
}

void cmd_shutdown()
{
	g_free(searchTree);
}

void cmd_add(const char* name, TFuncPtr ptr)
{
	TCmdItem *item;

	g_alloc(item, TCmdItem);
	item->type = CMDTYPE_BASIC;
	item->cmd.asBasic = ptr;

	searchTree->addNode(name, item);	
}

void cmd_add(const char* name, const char* cmd)
{
	TCmdItem *item;

	g_alloc(item, TCmdItem);
	item->type = CMDTYPE_COMBO;
	g_strdup(item->cmd.asCombo, cmd);

	searchTree->addNode(name, item);
}

void cmd_delete(const char* name)
{
	searchTree->deleteNode(name);
}

void cmd_exec(const char* command)
{	
	if (parseError)
		return;

	char *oldCmdLine = cmd_line;
	int oldCmdPos = cmd_pos;

	cmd_line = (char*)command;
	cmd_pos = 0;

	bool rpt = true;

	while (rpt&&(!parseError))
	{
		int sz = cmd_getToken();
		rpt = false;

		if (sz>0)
		{
			TCmdItem *item = (TCmdItem*)searchTree->getNode(cmd_token);
			if (item)
			{
				if (item->type == CMDTYPE_BASIC)
					item->cmd.asBasic();
				else
				{
					if (stackDepth<MAX_STACK_DEPTH)
					{
						stackDepth++;
						cmd_exec(item->cmd.asCombo);
						stackDepth--;
					}
					else
					{
						parseError = true;
						io_error("Error: stack overflow\n");
					}
				}
			}
			else
			{
				io_error("Error: unknown command \"%s\"\n",cmd_token);
				parseError = true;
			}
		}

		if (!parseError)
		{
			skipWhitespace();
			if (cmd_peekChar()==';')
			{
				cmd_getChar();
				rpt = true;
			}
			else
				if (command[cmd_pos])
				{
					io_error("Error: expected end of command, found \"%s\"\n", &command[cmd_pos]);
					parseError = true;
				}
		}
	}
	if (stackDepth==0)
		parseError = false;

	cmd_line = oldCmdLine;
	cmd_pos = oldCmdPos;
}

int cmd_peekToken()
{
	skipWhitespace();

	int len = 0;
	int p = cmd_pos;

	if (cmd_line[p]==0)
		return -1;

	if (cmd_line[p] == '"')
	{
		cmd_token[len++] = '"';
		p++;
		while (cmd_line[p] && (cmd_line[p]!='"')||((cmd_line[p]=='"')&&(cmd_line[p-1]=='\\')))
			cmd_token[len++] = cmd_line[p++];
		cmd_token[len++] = cmd_line[p];
		cmd_token[len] = 0;
		return len;
	}
	else
	{
		char c = cmd_line[p];

		while (c && (((c>='a')&&(c<='z')) || ((c>='A')&&(c<='Z')) || ((c>='0')&&(c<='9')) || (c=='_') || (c=='-') || (c=='+') || (c=='.')))
		{
			cmd_token[len++] = c;
			c = cmd_line[++p];
		}

		cmd_token[len] = 0;

		return len;
	}
}

int cmd_getToken()
{
	int len = cmd_peekToken();

	if (len>0)
		cmd_pos+=len;

	return len;
}

char cmd_peekChar()
{
	return cmd_line[cmd_pos];
}

char cmd_getChar()
{
	if (cmd_line[cmd_pos])
		return cmd_line[cmd_pos++];

	return 0;
}

char *cmd_getCombo(const char* name)
{
	TCmdItem *item = (TCmdItem*)searchTree->getNode(name);
	if (item && (item->type==CMDTYPE_COMBO))
		return item->cmd.asCombo;

	return NULL;		
}

int cmd_cleanTokenString()
{
	int p1=1;
	int p2=0;

	while (cmd_token[p1+1])
	{
		if ((cmd_token[p1]=='\\')&&(cmd_token[p1+1]=='"'))
		{
			cmd_token[p2++]='"';
			p1+=2;
		}
		else
		{
			cmd_token[p2++] = cmd_token[p1++];
		}
	}
	cmd_token[p2]=0;

	return p2;
}

void cmd_parseFile(char *fname)
{
	FILE *f = fopen(fname, "rt");

	if (!f)
		return;

	char cmd[1024];

	while (!feof(f))
	{
		if (fgets(cmd, 1024, f))
			cmd_exec(cmd);
	}

	fclose(f);
}
